﻿using System.Collections.Generic;
using Godot.core;
using Godot.core.os;

namespace Godot.core.bind
{
    using SysMethodInfo = System.Reflection.MethodInfo;

    public partial class CoreBindThread : Reference
    {
        protected Variant _ret;
        protected Variant _userdata;
        protected volatile bool _active;
        protected Object _target_instance;
        protected StringName _target_method;
        protected Thread _thread;

        protected static new void _bind_methods()
        {
            ObjectTypeDB.bind_method("start:Error", typeof(CoreBindThread).GetMethod("start"), null, (int)Thread.Priority.PRIORITY_NORMAL);
	        ObjectTypeDB.bind_method("get_id", typeof(CoreBindThread).GetMethod("get_id"));
	        ObjectTypeDB.bind_method("is_active", typeof(CoreBindThread).GetMethod("is_active"));
	        ObjectTypeDB.bind_method("wait_to_finish:var", typeof(CoreBindThread).GetMethod("wait_to_finish"));

            ObjectTypeDB.bind_integer_constant(get_type_static(), "PRIORITY_LOW", (int) Thread.Priority.PRIORITY_LOW);
            ObjectTypeDB.bind_integer_constant(get_type_static(), "PRIORITY_NORMAL", (int)Thread.Priority.PRIORITY_NORMAL);
            ObjectTypeDB.bind_integer_constant(get_type_static(), "PRIORITY_HIGH", (int)Thread.Priority.PRIORITY_HIGH);
        }

        protected static void _start_func(object ud)
        {
            CoreBindThread t = (CoreBindThread)ud;
            Variant.CallError ce = new Variant.CallError();
            Variant[] args = new Variant[1] { t._userdata };
            t._ret = t._target_instance.call(t._target_method, args, ref ce);
            if (ce.error != Variant.CallError.Error.CALL_OK)
            {
                string reason = "";
		        switch(ce.error)
                {
			        case Variant.CallError.Error.CALL_ERROR_INVALID_ARGUMENT: {

				        reason = "Invalid Argument #" + ce.argument;
			        } break;

			        case Variant.CallError.Error.CALL_ERROR_TOO_MANY_ARGUMENTS: {
				        reason = "Too Many Arguments";
			        } break;

			        case Variant.CallError.Error.CALL_ERROR_TOO_FEW_ARGUMENTS: {
				        reason = "Too Few Arguments";
			        } break;

			        case Variant.CallError.Error.CALL_ERROR_INVALID_METHOD: {
				        reason = "Method Not Found";
			        } break;

			        default: break;
                }
            }
        }

        public CoreBindThread()
        {
            _active = false;
            _thread = null;
            _target_instance = null;
        }

        public string get_id()
        {
            if (_thread == null)
                return "";
            return _thread.ID.ToString();
        }

        public bool is_active()
        {
            return _active;
        }

        public Error start(Object p_instance, StringName p_method, Variant p_userdata = null,int p_priority = (int)Thread.Priority.PRIORITY_NORMAL)
        {
            if(_active) 
                return Error.ERR_ALREADY_IN_USE;

	        if(p_instance == null) 
                return Error.ERR_INVALID_PARAMETER;

	        if(p_method == null || p_method == "") 
                return Error.ERR_INVALID_PARAMETER;

	        if(p_priority >= 3)
                return Error.ERR_INVALID_PARAMETER;


	        _ret = new Variant();
	        _target_method = p_method;
	        _target_instance = p_instance;
	        _userdata = p_userdata;
	        _active = true;

	        Thread.Settings s = new Thread.Settings();
	        s.priority = (Thread.Priority)p_priority;
	        _thread = Thread.create(_start_func, this, s);

	        if (_thread == null) 
            {
		        _active = false;
		        _target_method = null;
		        _target_instance = null;
		        _userdata = new Variant();
		        return Error.ERR_CANT_CREATE;
	        }
            return Error.OK;
        }

        public Variant wait_to_finish()
        {
            if(_thread == null)
                return null;

	        if(!_active)
                return null;

	        Thread.wait_to_finish(_thread);
	        
            Variant r = _ret;
	        _active = false;
            _target_method = null;
	        _target_instance = null;
	        _userdata = null;
	        _thread = null;
	        return r;
        }
    }

    public partial class CoreBindThread
    {
        #region OBJ_TYPE(CoreBindThread,Reference)
        public override string get_type() { return "CoreBindThread"; }

        public override string get_save_type() { return get_type(); } //type stored when saving

        public override StringName get_type_name() { return (StringName)get_type_static(); }

        public override bool is_type(string p_type) { return (p_type == "CoreBindThread") ? true : base.is_type(p_type); }

        protected override void _initialize_typev() { initialize_type(); }

        protected override bool _setv(StringName p_name, Variant p_property)
        {
            if (base._setv(p_name, p_property))
                return true;

            if (typeof(CoreBindThread).GetMethod("_set", Misc.BindingFlags_Greedy) != typeof(Reference).GetMethod("_set", Misc.BindingFlags_Greedy))
            {
                return _set(p_name, p_property);
            }

            return false;
        }

        protected override bool _getv(StringName p_name, ref Variant r_property)
        {
            if (typeof(CoreBindThread).GetMethod("_get", Misc.BindingFlags_Greedy) != typeof(Reference).GetMethod("_get", Misc.BindingFlags_Greedy))
            {
                if (_get(p_name, ref r_property))
                    return true;
            }

            return base._getv(p_name, ref r_property);
        }

        protected override void _get_property_listv(ref List<PropertyInfo> p_list, bool p_reversed)
        {
            if (!p_reversed)
            {
                base._get_property_listv(ref p_list, p_reversed);
            }

            p_list.Add(new PropertyInfo(Variant.Type.NIL, get_type_static(), PropertyHint.PROPERTY_HINT_NONE, "", (int)PropertyUsageFlags.PROPERTY_USAGE_CATEGORY));

            if (!_is_gpl_reversed())
                ObjectTypeDB.get_property_list("CoreBindThread", ref p_list, true);

            if (typeof(CoreBindThread).GetMethod("_get_property_list", Misc.BindingFlags_Greedy) != typeof(Reference).GetMethod("_get_property_list", Misc.BindingFlags_Greedy))
            {
                _get_property_list(ref p_list);
            }

            if (_is_gpl_reversed())
                ObjectTypeDB.get_property_list("CoreBindThread", ref p_list, true);

            if (p_reversed)
            {
                base._get_property_listv(ref p_list, p_reversed);
            }
        }

        protected override void _notificationv(int p_notification, bool p_reversed)
        {
            if (!p_reversed)
                base._notificationv(p_notification, p_reversed);

            if (typeof(CoreBindThread).GetMethod("_notification", Misc.BindingFlags_Greedy) != typeof(Reference).GetMethod("_notification", Misc.BindingFlags_Greedy))
            {
                _notification(p_notification);
            }

            if (p_reversed)
                base._notificationv(p_notification, p_reversed);
        }

        public static new string get_type_static() { return "CoreBindThread"; }

        public static new string get_parent_type_static() { return Reference.get_type_static(); }

        public static new void get_inheritance_list_static(ref List<string> r_inheritance_list)
        {
            Reference.get_inheritance_list_static(ref r_inheritance_list);
            r_inheritance_list.Add(get_type_static());
        }

        public static new string get_category_static()
        {
            string category = Reference.get_category_static();
            if (typeof(CoreBindThread).GetMethod("_get_category", Misc.BindingFlags_Greedy) != typeof(Reference).GetMethod("_get_category", Misc.BindingFlags_Greedy))
            {
                if (category != "")
                    category += "/";
                category += _get_category();
            }
            return category;
        }

        public static new string inherits_static() { return "Reference"; }

        public static new void get_valid_parents_static(ref List<string> p_parents)
        {
            if (typeof(CoreBindThread).GetMethod("_get_valid_parents_static", Misc.BindingFlags_Greedy) != typeof(Reference).GetMethod("_get_valid_parents_static", Misc.BindingFlags_Greedy))
            {
                CoreBindThread._get_valid_parents_static(ref p_parents);
            }

            Reference.get_valid_parents_static(ref p_parents);
        }

        public static new void initialize_type()
        {
            if (__initialized)
                return;
            Reference.initialize_type();
            ObjectTypeDB._add_type<CoreBindThread>();

            SysMethodInfo cls_bind = typeof(CoreBindThread).GetMethod("_bind_methods", Misc.BindingFlags_Greedy);
            SysMethodInfo par_bind = typeof(Reference).GetMethod("_bind_methods", Misc.BindingFlags_Greedy);
            if (cls_bind != par_bind)
                cls_bind.Invoke(null, null);
            __initialized = true;
        }

        private static bool __initialized = false;
        #endregion
    }
}
